﻿using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Guids;
using Volo.Abp.Timing;

namespace Xms.Abp.BackgroundJob;

/// <summary>
/// Default implementation of <see cref="IBackgroundJobManager"/>.
/// </summary>
[Dependency(ReplaceServices = true)]
public class DefaultBackgroundJobManager : IBackgroundJobManager, ITransientDependency
{
    protected IClock Clock => LazyInject<IClock>();
    protected IBackgroundJobSerializer Serializer => LazyInject<IBackgroundJobSerializer>();
    protected IGuidGenerator GuidGenerator => LazyInject<IGuidGenerator>();
    protected IBackgroundJobStore Store => LazyInject<IBackgroundJobStore>();
    protected XmsBackgroundJobTriggerOptions BackgroundJobOptions => LazyInject<IOptions<XmsBackgroundJobTriggerOptions>>().Value;
    protected IAbpLazyServiceProvider AbpLazyServiceProvider { get; }
    protected IServiceScopeFactory ServiceScopeFactory { get; }

    public DefaultBackgroundJobManager(IAbpLazyServiceProvider lazyServiceProvider, IServiceScopeFactory serviceScopeFactory)
    {
        AbpLazyServiceProvider = lazyServiceProvider;
        ServiceScopeFactory = serviceScopeFactory;
    }

    protected T LazyInject<T>()
    {
        return AbpLazyServiceProvider.LazyGetRequiredService<T>();
    }

    public virtual async Task<Guid> EnqueueAsync<TArgs>(
        TArgs args,
        Guid? tenantId,
        BackgroundJobPriority priority = BackgroundJobPriority.Normal,
        TimeSpan? delay = null,
        int? timeout = null,
        short? maxTryCount = null,
        IEnumerable<int> retryIntervals = null,
        IEnumerable<Guid> referenceJobs = null,
        Guid? scheduleId = null,
        CancellationToken stoppingToken = default)
    {
        var jobConfiguration = BackgroundJobOptions.GetJob<TArgs>();
        var jobName = jobConfiguration.JobName;
        var displayName = jobConfiguration.DisplayName;
        var jobId = await EnqueueAsync(jobName, displayName, args, tenantId, priority, delay, timeout, maxTryCount, retryIntervals, referenceJobs, scheduleId, stoppingToken);
        return jobId;
    }

    public virtual async Task<Guid> EnqueueAsync(
        string jobName,
        string displayName,
        object args,
        Guid? tenantId,
        BackgroundJobPriority priority = BackgroundJobPriority.Normal,
        TimeSpan? delay = null,
        int? timeout = null,
        short? maxTryCount = null,
        IEnumerable<int> retryIntervals = null,
        IEnumerable<Guid> referenceJobs = null,
        Guid? scheduleId = null,
        CancellationToken stoppingToken = default)
    {
        var jobArgs = args is string argsStr ? argsStr : Serializer.Serialize(args);
        var jobInfo = new BackgroundJobInfo
        {
            Id = GuidGenerator.Create(),
            Name = jobName,
            DisplayName = displayName,
            JobArgs = jobArgs,
            Priority = priority,
            CreationTime = Clock.Now,
            NextTryTime = Clock.Now,
            Timeout = timeout,
            TenantId = tenantId,
            MaxTryCount = maxTryCount,
            IsAbandoned = false,
            LastTryTime = DateTime.MinValue,
            ScheduleId = scheduleId,
            TryCount = 0,
            BackgroundJobStatus = BackgroundJobStatus.Waiting,
        };

        if (referenceJobs != null)
        {
            jobInfo.ReferenceJobs.AddRange(referenceJobs);
        }

        if (retryIntervals != null)
        {
            jobInfo.RetryIntervals.AddRange(retryIntervals);
        }

        if (delay.HasValue)
        {
            jobInfo.NextTryTime = Clock.Now.Add(delay.Value);
        }

        await Store.InsertAsync(jobInfo, stoppingToken);

        return jobInfo.Id;
    }

    public bool IsAvailable()
    {
        return true;
    }
}
